package com.ehome.serial.camera

import android.os.IBinder.DeathRecipient
import android.os.Looper
import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.MODE_CREATE
import android.os.ParcelFileDescriptor.MODE_TRUNCATE
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
import android.os.ParcelFileDescriptor.MODE_WRITE_ONLY
import android.os.ParcelFileDescriptor.MODE_READ_WRITE
import android.os.ServiceManager
import android.os.ServiceSpecificException
import android.util.Log
import com.ehome.ba330.IBA330CameraService
import com.google.gson.Gson
import java.io.File
import java.io.FileNotFoundException
import kotlin.concurrent.thread
import kotlin.math.log


@OptIn(ExperimentalUnsignedTypes::class)
class BA330CameraManager private constructor() {
    companion object {
        private val SET_OTA_FILE: Byte = 0x03.toByte()
        private val SET_CALIBRATION_FILE: Byte = 0x21.toByte()
        private val GET_CALIBRATION_FILE: Byte = 0x22.toByte()
        private val GET_FACTORY_QC_FILE: Byte = 0x23.toByte()
        private val SET_FACTORY_QC_FILE: Byte = 0x24.toByte()
        private val GET_LOG_FILE: Byte = 0x04.toByte()
        private val GET_RAW_PICTURE: Byte = 0x07.toByte()
        private val GET_YVU_PICTURE: Byte = 0x08.toByte()
        private val GOLD_RAW_PICTURE: Byte = 0x00.toByte()
        private val FLUO_RAW_PICTURE: Byte = 0x01.toByte()
        private val QRCODE_YUV_PICTURE: Byte = 0x00.toByte()
        private val CONTROL_COMMAND_SLEF_CHECK = 0x00.toByte()

        //        private val CONTROL_COMMAND_CLEAN_LOG = 0x01.toByte()
        //        private val CONTROL_COMMAND_CLEAN_REBOOT = 0x02.toByte()

        private val CONTROL_COMMAND_UPDATE = 0x03.toByte()

        val EXPOSURE_SEQUENCE = intArrayOf(
            15000,
            22000,
            33000,
            50000,
            75000,
            113000,
            170000,
            256000,
            384000,
            576000,
            864000,
            1297000,
            1946000,
            2919000,
            4378000,
            6568000,
            9852000,
            14778000,
            22168000,
            33252000,
            49878000,
            74818000,
            112227000
        )
        val DEFAULT_YUV_EXPOSURE_TIME = 1500000
        val DEFAULT_YUV_BRIGHTNESS: Byte = 20

        val DEFAULT_GOLD_EXPOSURE_TIME = 1500000
        val DEFAULT_GOLD_BRIGHTNESS: Byte = 20

        val DEFAULT_FLUO_EXPOSURE_TIME = 110000000
        val DEFAULT_FLUO_BRIGHTNESS: Byte = 20

        val instance: BA330CameraManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            BA330CameraManager()
        }
    }

    var ba330Service: IBA330CameraService? = null

    private val lock = Any()

    fun check() {
        if (Looper.getMainLooper() == Looper.myLooper()) {
            throw Exception("IO on MainThread Exception")
        }
        if (ba330Service == null || ba330Service?.asBinder()!!.isBinderAlive == false) {
            ba330Service =
                IBA330CameraService.Stub.asInterface(ServiceManager.getService("ba330"))
//            if (ba330Service ==  null){
//                thread(start = true) {
//                    startup()
//                }
//                ba330Service = IBA330CameraService.Stub.asInterface(ServiceManager.waitForService("ba330"))
//            }
            ba330Service!!.asBinder().linkToDeath(object : DeathRecipient {
                override fun binderDied() {
                    ba330Service!!.asBinder().unlinkToDeath(this, 0)
                    ba330Service = null
                }
            }, 0)
        }
    }

    fun getDeviceInfo(): BA330DeviceInfoBean {
        synchronized(lock) {
            Log.d("BA330CameraManager", "getDeviceInfo begin...")
            check()
            var result: String? = null
            try {
                result = ba330Service!!.getDeviceInfo()
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            }
            Log.d("BA330CameraManager", "getDeviceInfo end...")
            return Gson().fromJson(result, BA330DeviceInfoBean::class.java)
        }
    }

    fun setSerialNumber(serialNumber: String) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "setSerialNumber begin...")
            check()
            try {
                ba330Service!!.setSerialNumber(serialNumber)
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            }
            Log.d("BA330CameraManager", "setSerialNumber end...")
        }
    }

    /**
     * 写入升级文件
     */
    fun setOtaFile(source: File) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "setOtaFile begin...")
            check()
            if (!source.exists()) {
                throw FileNotFoundException(source.absolutePath)
            }
            if (source.isDirectory) {
                throw Exception("Target ${source.absolutePath} is directory")
            }
            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(source, ParcelFileDescriptor.MODE_READ_ONLY)
                ba330Service!!.setFile(SET_OTA_FILE, pfd)
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "setOtaFile end...")
        }
    }

    fun setCalibrationFile(source: File) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "setCalibrationFile begin...")
            check()
            if (!source.exists()) {
                throw FileNotFoundException(source.absolutePath)
            }
            if (source.isDirectory) {
                throw Exception("Target ${source.absolutePath} is directory")
            }
            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(source, ParcelFileDescriptor.MODE_READ_ONLY)
                ba330Service!!.setFile(SET_CALIBRATION_FILE, pfd)
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "setCalibrationFile end...")
        }
    }

    fun getCalibrationFile(target: File) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "getCalibrationFile begin...")
            check()
            if (target.isDirectory) {
                throw Exception("Target ${target.absolutePath} is directory")
            }
            target.parentFile?.exists()?.let {
                if (!it) {
                    throw Exception("Parent ${target.parentFile?.absolutePath} not exists")
                }
            }
            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(
                    target,
                    MODE_CREATE.or(MODE_TRUNCATE).or(MODE_WRITE_ONLY)
                )
                ba330Service!!.getFile(GET_CALIBRATION_FILE, ByteArray(0), pfd)
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "getCalibrationFile end...")
        }
    }

    /**
     * 写入工厂质控文件
     */
    fun setFactoryQCFile(source: File) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "setFactoryQCFile begin...")
            check()
            if (!source.exists()) {
                throw FileNotFoundException(source.absolutePath)
            }
            if (source.isDirectory) {
                throw Exception("Target ${source.absolutePath} is directory")
            }
            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(source, MODE_READ_ONLY)
                ba330Service!!.setFile(SET_FACTORY_QC_FILE, pfd)
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "setFactoryQCFile end...")
        }
    }

    /**
     * 读取工厂质控文件
     */
    fun getFactoryQCFile(target: File) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "getFactoryQCFile begin...")
            check()
            if (target.isDirectory) {
                throw Exception("Target ${target.absolutePath} is directory")
            }
            target.parentFile?.exists()?.let {
                if (!it) {
                    throw Exception("Parent ${target.parentFile?.absolutePath} not exists")
                }
            }

            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(
                    target,
                    MODE_CREATE.or(MODE_TRUNCATE).or(MODE_WRITE_ONLY)
                )
                ba330Service!!.getFile(GET_FACTORY_QC_FILE, ByteArray(0), pfd)
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "getFactoryQCFile end...")
        }
    }

    fun takeFluoRawPicture(
        exposureTime: Int = DEFAULT_FLUO_EXPOSURE_TIME,
        target: File,
        brightness: Byte = DEFAULT_FLUO_BRIGHTNESS
    ) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "takeFluoRawPicture begin...")
            check()
            if (target.isDirectory) {
                throw Exception("Target ${target.absolutePath} is directory")
            }
            target.parentFile?.exists()?.let {
                if (!it) {
                    throw Exception("Parent ${target.parentFile?.absolutePath} not exists")
                }
            }
            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(
                    target,
                    MODE_CREATE.or(MODE_TRUNCATE).or(MODE_WRITE_ONLY)
                )
                ba330Service!!.getFile(
                    GET_RAW_PICTURE,
                    byteArrayOf(FLUO_RAW_PICTURE) + exposureTime.toByteArray() + byteArrayOf(
                        brightness
                    ),
                    pfd
                )
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "takeFluoRawPicture end...")
        }
    }

    fun takeGoldRawPicture(
        exposureTime: Int = DEFAULT_GOLD_EXPOSURE_TIME,
        target: File,
        brightness: Byte = DEFAULT_GOLD_BRIGHTNESS
    ) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "takeGoldRawPicture begin...")
            check()
            if (target.isDirectory) {
                throw Exception("Target ${target.absolutePath} is directory")
            }
            target.parentFile?.exists()?.let {
                if (!it) {
                    throw Exception("Parent ${target.parentFile?.absolutePath} not exists")
                }
            }

            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(
                    target,
                    MODE_CREATE.or(MODE_TRUNCATE).or(MODE_WRITE_ONLY)
                )
                ba330Service!!.getFile(
                GET_RAW_PICTURE,
                byteArrayOf(GOLD_RAW_PICTURE) + exposureTime.toByteArray() +
                        byteArrayOf(brightness.toByte()),
                    pfd
            )
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "takeGoldRawPicture end...")
        }
    }

    fun takeYuvPicture(
        exposureTime: Int = DEFAULT_YUV_EXPOSURE_TIME,
        target: File,
        brightness: Byte = DEFAULT_YUV_BRIGHTNESS
    ) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "takeYuvPicture begin...")
            check()
            if (target.isDirectory) {
                throw Exception("Target ${target.absolutePath} is directory")
            }
            target.parentFile?.exists()?.let {
                if (!it) {
                    throw Exception("Parent ${target.parentFile?.absolutePath} not exists")
                }
            }

            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(
                    target,
                    MODE_CREATE.or(MODE_TRUNCATE).or(MODE_WRITE_ONLY)
                )
                ba330Service!!.getFile(
                GET_YVU_PICTURE,
                byteArrayOf(QRCODE_YUV_PICTURE) + exposureTime.toByteArray() +
                        byteArrayOf(brightness.toByte()),
                    pfd
            )
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "takeYuvPicture end...")
        }
    }

    fun getLogFile(target: File) {
        synchronized(lock) {
            Log.d("BA330CameraManager", "getLogFile begin...")
            check()
            if (target.isDirectory) {
                throw Exception("Target ${target.absolutePath} is directory")
            }
            target.parentFile?.exists()?.let {
                if (!it) {
                    throw Exception("Parent ${target.parentFile?.absolutePath} not exists")
                }
            }
            var pfd: ParcelFileDescriptor? = null
            try {
                pfd = ParcelFileDescriptor.open(
                    target,
                    MODE_CREATE.or(MODE_TRUNCATE).or(MODE_WRITE_ONLY)
                )
                ba330Service!!.getFile(
                GET_LOG_FILE,
                0x00.toByteArray(),
                    pfd
            )
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            } finally {
                pfd?.close()
            }
            Log.d("BA330CameraManager", "getLogFile end...")
        }
    }

    fun getKitSate(): Int {
        synchronized(lock) {
            Log.d("BA330CameraManager", "getKitSate begin...")
            check()
            try {
                val state = ba330Service!!.kitState
                Log.d("BA330CameraManager", "getKitSate end...")
                return state
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            }
        }
    }

    fun takeFluoSequencePicture(sequence: LinkedHashMap<Int, File>, brightness: Byte = DEFAULT_FLUO_BRIGHTNESS){
        synchronized(lock){
            Log.d("BA330CameraManager", "takeFluoSequencePicture begin...")
            check()
            val hashMap = LinkedHashMap<Int, ParcelFileDescriptor>()
            for ((key ,value) in sequence){
                if (value.isDirectory) {
                    throw Exception("Target ${value.absolutePath} is directory")
                }
                value.parentFile?.exists()?.let {
                    if (!it) {
                        throw Exception("Parent ${value.parentFile?.absolutePath} not exists")
                    }
                }
                hashMap.put(
                    key,
                    ParcelFileDescriptor.open(
                        value,
                        MODE_CREATE.or(MODE_TRUNCATE).or(MODE_WRITE_ONLY)
                    )
                )
            }
            try {
                ba330Service!!.takeFluoSequencePicture(
                    hashMap.keys.toIntArray(),
                    hashMap.values.toTypedArray(),
                    brightness
                )
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            }finally {
                for ((_ ,value) in hashMap){
                    value.close()
                }
            }
            Log.d("BA330CameraManager", "takeFluoSequencePicture end...")
        }
    }

    fun selfCheck() {
        synchronized(lock) {
            Log.d("BA330CameraManager", "selfCheck begin...")
            check()
            val data = ByteArray(1024) { 0xAA.toByte() }
            try {
                ba330Service!!.controlCommand(CONTROL_COMMAND_SLEF_CHECK, data)
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            }
            Log.d("BA330CameraManager", "selfCheck end...")
        }
    }

    fun update() {
        synchronized(lock) {
            Log.d("BA330CameraManager", "update begin...")
            check()
            try {
                ba330Service!!.controlCommand(CONTROL_COMMAND_UPDATE, ByteArray(0))
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            }
            Log.d("BA330CameraManager", "update end...")
        }
    }

    fun reboot() {
        synchronized(lock) {
            Log.d("BA330CameraManager", "reboot begin...")
            check()
            try {
                ba330Service!!.reboot()
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            }
            Log.d("BA330CameraManager", "reboot end...")
        }
    }

    fun setTimeStamp() {
        synchronized(lock) {
            Log.d("BA330CameraManager", "setTimeStamp begin...")
            check()
            try {
                ba330Service!!.setTimeStamp()
            } catch (e: ServiceSpecificException) {
                if (e.errorCode == IBA330CameraService.BA330_CONNECTION_EXCEPTION) {
                    throw BA330ConnectionException(e.message)
                } else {
                    throw BA330CommunicationException(e.message)
                }
            }
            Log.d("BA330CameraManager", "setTimeStamp end...")
        }
    }

    fun Int.toByteArray(): ByteArray {
        return ByteArray(4) { index ->
            ((this shr (index * 8)) and 0xFF).toByte()
        }
    }
}